#include <mongoc.h>
#include <string.h>
#include "mongoc_const.h"
#include "mongoc_data.h"

size_t nbundle = 0;
struct MongoObjectBundle bundles[100];

int mongoc_connect(const char *uri_str, const char *db_name, const char *col_name, int *cid) {
  if (nbundle == 100) return FORTRAN_MONGO_MEMORY_ERROR;

  printf("[Notice]: Connect to MongoDB %s %s %s.\n", uri_str, db_name, col_name);

  mongoc_uri_t *uri;
  bson_error_t error;
  struct MongoObjectBundle *bundle = &bundles[nbundle];

  /*
   * Check if URI argument is valid.
   */
  uri = mongoc_uri_new_with_error(uri_str, &error);
  if (!uri) {
    printf("%s\n", uri_str);
    printf("%s\n", error.message);
    return FORTRAN_MONGO_URI_NEW_ERROR;
  }

  /*
   * Create a new client instance.
   */
  bundle->client = mongoc_client_new_from_uri(uri);
  if (!bundle->client) return FORTRAN_MONGO_CLIENT_NEW_ERROR;

  /*
   * Get database and collection handles.
   */
  bundle->db_name = malloc(strlen(db_name) * sizeof(char));
  strcpy(bundle->db_name, db_name);
  bundle->db = mongoc_client_get_database(bundle->client, db_name);
  bundle->col_name = malloc(strlen(col_name) * sizeof(char));
  strcpy(bundle->col_name, col_name);
  bundle->col = mongoc_client_get_collection(bundle->client, db_name, col_name);

  /*
   * Destroy unused variables.
   */
  mongoc_uri_destroy(uri);

  /*
   * Return database ID as bundle number.
   */
  *cid = (int) nbundle++;

  return FORTRAN_MONGO_NO_ERROR;
}

int mongoc_create_index(int *cid, const char *index_name) {
  if (*cid >= nbundle) return FORTRAN_MONGO_MEMORY_ERROR;

  struct MongoObjectBundle *bundle = &bundles[*cid];
  bson_t keys;
  bson_t *create_indexes;
  bson_t reply;
  bson_error_t error;

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, index_name, "text");
  char *name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
        "unique", BCON_BOOL(true),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: createIndexes: %s\n", error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_free(name);
  bson_destroy(&reply);
  bson_destroy(create_indexes);
}

int mongoc_create_platform_index(int *cid) {
  if (*cid >= nbundle) return FORTRAN_MONGO_MEMORY_ERROR;

  struct MongoObjectBundle *bundle = &bundles[*cid];
  bson_t keys;
  char *name;
  bson_t *create_indexes;
  bson_t reply;
  bson_error_t error;

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, "sid", "text");
  name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
        "unique", BCON_BOOL(true),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: mongoc_create_platform_index: %s\n", error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, "location", "2dsphere");
  name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
        "unique", BCON_BOOL(true),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: mongoc_create_platform_index: %s\n", error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_free(name);
  bson_destroy(&reply);
  bson_destroy(create_indexes);
}

int mongoc_create_record_index(int *cid) {
   if (*cid >= nbundle) return FORTRAN_MONGO_MEMORY_ERROR;

  struct MongoObjectBundle *bundle = &bundles[*cid];
  bson_t keys;
  bson_t *create_indexes;
  bson_t reply;
  bson_error_t error;

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, "sid", "text");
  BSON_APPEND_INT32(&keys, "time", 1);
  char *name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
        "unique", BCON_BOOL(true),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: mongoc_create_record_index: %s\n", error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_free(name);
  bson_destroy(&reply);
  bson_destroy(create_indexes);
}

int mongoc_create_drift_record_index(int *cid) {
   if (*cid >= nbundle) return FORTRAN_MONGO_MEMORY_ERROR;

  struct MongoObjectBundle *bundle = &bundles[*cid];
  bson_t keys;
  bson_t *create_indexes;
  bson_t reply;
  bson_error_t error;

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, "sid", "text");
  BSON_APPEND_INT32(&keys, "time", 1);
  char *name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
        "unique", BCON_BOOL(true),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: mongoc_create_drift_record_index:%d: %s\n", __LINE__, error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_init(&keys);
  BSON_APPEND_UTF8(&keys, "location", "2dsphere");
  name = mongoc_collection_keys_to_index_string(&keys);
  create_indexes = BCON_NEW(
    "createIndexes", BCON_UTF8(bundle->col_name),
    "indexes", "[",
      "{",
        "key", BCON_DOCUMENT(&keys),
        "name", BCON_UTF8(name),
      "}",
    "]"
  );
  if (!mongoc_database_write_command_with_opts(bundle->db, create_indexes, NULL, &reply, &error)) {
    printf("[Error]: mongoc_create_drift_record_index:%d: %s\n", __LINE__, error.message);
    return FORTRAN_MONGO_WRITE_COMMAND_ERROR;
  }

  bson_free(name);
  bson_destroy(&reply);
  bson_destroy(create_indexes);
}

void mongoc_final() {
  for (int i = 0; i < nbundle; i++) {
    free(bundles[i].col_name);
    free(bundles[i].db_name);
    mongoc_collection_destroy(bundles[i].col);
    mongoc_database_destroy(bundles[i].db);
    mongoc_client_destroy(bundles[i].client);
  }
  mongoc_cleanup();
}
